/*
 *   Copyright 2012, Thomas Kerber
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package milk.jpatch.classLevel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import milk.jpatch.CPoolMap;
import milk.jpatch.Util;
import milk.jpatch.access.AccessFlagsPatch;
import milk.jpatch.attribs.AttributesPatch;

import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Method;

/**
 * Patch denoting a method being modified.
 * @author Thomas Kerber
 * @version 1.0.0
 */
public class MethodModif extends MethodPatch{
    
    private static final long serialVersionUID = -9170485812513205093L;
    
    /**
     * The component flags patch.
     */
    private final AccessFlagsPatch flagsPatch;
    /**
     * The patch of the return type. Null implies ID patch, anything else a
     * replace.
     */
    private final String retPatch;
    /**
     * The component attributes patch.
     */
    private final AttributesPatch attribsPatch;
    
    /**
     * Creates.
     * @param identifier The methods identifier.
     * @param retPatch The patch for the methods return type. Null means ID
     *     patch, anything else a replace patch.
     * @param flagsPatch The component flags patch.
     * @param attribsPatch The component attributes patch.
     */
    public MethodModif(String identifier, String retPatch,
            AccessFlagsPatch flagsPatch, AttributesPatch attribsPatch){
        super(identifier);
        this.retPatch = retPatch;
        this.flagsPatch = flagsPatch;
        this.attribsPatch = attribsPatch;
    }
    
    /**
     * Generates.
     * @param old The old method.
     * @param new_ The new method.
     * @param map The cpool map.
     * @return The patch.
     */
    public static MethodModif generate(Method old, Method new_, CPoolMap map){
        String identifier = Util.getMethodIdentifier(old);
        String retPatch = Util.getMethodReturn(new_);
        if(Util.getMethodReturn(old).equals(retPatch))
            retPatch = null;
        AccessFlagsPatch flagsPatch = AccessFlagsPatch.generate(
                old.getAccessFlags(),
                new_.getAccessFlags(),
                AccessFlagsPatch.AccessLevel.METHOD);
        AttributesPatch attribsPatch = AttributesPatch.generate(
                old.getAttributes(),
                new_.getAttributes(),
                map);
        
        return new MethodModif(identifier, retPatch, flagsPatch, attribsPatch);
    }
    
    /**
     * 
     * @return The component flags patch.
     */
    public AccessFlagsPatch getFlagsPatch(){
        return flagsPatch;
    }
    
    /**
     * 
     * @return The return type patch. Null represents ID, anything else a
     *     replace.
     */
    public String getReturnPatch(){
        return retPatch;
    }
    
    /**
     * 
     * @return The component attributes patch.
     */
    public AttributesPatch getAttributesPatch(){
        return attribsPatch;
    }
    
    @Override
    public Method patch(Method m, CPoolMap map){
        if(m == null)
            throw new NullPointerException(
                    "Cannot modify nonexistant method.");
        int flags = flagsPatch.patch(m.getAccessFlags());
        String ret;
        if(retPatch != null)
            ret = retPatch;
        else
            ret = Util.getMethodReturn(m);
        List<Attribute> attribs = new ArrayList<Attribute>(attribsPatch.patch(
                new ArrayList<Attribute>(Arrays.asList(m.getAttributes())),
                map));
        Attribute[] attribArr = attribs.toArray(new Attribute[attribs.size()]);
        int sigIndex = Util.findConstantStringIn(map,
                Util.getMethodSig(identifier, ret));
        int nameIndex = Util.findConstantStringIn(map, m.getName());
        Method newMethod = new Method(flags, nameIndex, sigIndex, attribArr,
                map.to);
        return newMethod;
    }
}
